<?php

declare(strict_types=1);

namespace App\Middleware;

use App\Constants\StatusCode;
use App\Exception\BusinessException;
use App\Exception\JWTException;
use App\Foundation\Facades\Log;
use App\Service\Auth\UserService;
use Hyperf\HttpServer\Contract\RequestInterface;
use App\Foundation\Utils\JWT;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;

class AuthMiddleware implements MiddlewareInterface
{
    /**
     * @var ContainerInterface
     */
    protected $container;

    /**
     * @var RequestInterface
     */
    protected $request;

    /**
     * @var JWT
     */
    protected $jwt;

    public function __construct(ContainerInterface $container, RequestInterface $request, JWT $jwt)
    {
        $this->container = $container;
        $this->request = $request;
        $this->jwt = $jwt;
    }

    /**
     * 请求校验Token中间件
     * @param ServerRequestInterface $request
     * @param RequestHandlerInterface $handler
     * @return ResponseInterface
     * @throws \Psr\SimpleCache\InvalidArgumentException
     * @throws \Throwable
     */
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        $requireParams = $this->request->all();
        //记录请求参数日志记录
        if (config('request_log')) Log::requestLog()->info('请求参数：' . json_encode($requireParams));
        $token = $request->getHeaderLine('Authorization') ?? '';
        if (empty($token)) {
            throw new BusinessException(StatusCode::ERR_INVALID_TOKEN, '缺失Token');
        }
        $token = JWT::handleToken($token);
        if (!$token) {
            throw new BusinessException(StatusCode::ERR_INVALID_TOKEN, 'Token无效');
        }
        try {
            //如果校验成功
            if ($tokenData = $this->jwt->checkToken($token)) {
                //将用户信息放置协程上下文当中
                $userInfo = UserService::getInstance()->getUserInfoByToken($token);
                context_set('user_info', $userInfo);
                return $handler->handle($request);
            };
        } catch (JWTException $e) {
            // 此处捕获到了 token 过期所抛出的 JWTException
            //我们在这里需要做的是刷新该用户的 token 并将它添加到响应头中
            if ($e->getMessage() == 'expired') {
                // 刷新用户的 token
                Log::debug()->error('token:'.$token);
                $tokenData = $this->jwt->getParserData($token);
                //判断token是否在缓存时间内，如果是刷新token
                if (time() - $tokenData['exp'] < intval(config('jwt.ttl_cache'))) {
                    $token = $this->jwt->refreshToken($token);
                    //从协程获取全局的Response 对象， 并将刷新TOKEN写进去头部返回给前端供前端刷新token
                    $response = context_get(ResponseInterface::class);
                    //在返回对象里增添刷新后的token以及Access-Control-Expose-Headers（让前端可以获取到我们自定义的authorization）
                    $response = $response->withHeader('authorization', $token)->withHeader('Access-Control-Expose-Headers', 'authorization');
                    context_set(ResponseInterface::class, $response);
                    //将用户信息放置协程上下文当中
                    $userInfo = UserService::getInstance()->getUserInfoByToken($token);
                    context_set('user_info', $userInfo);
                    return $handler->handle($request);
                }
            }
            throw new BusinessException(StatusCode::ERR_INVALID_TOKEN, $e->getMessage());
        }
        throw new BusinessException(StatusCode::ERR_INVALID_TOKEN, 'Token无效或者过期');

    }
}
